package com.jlpay.open.jlpay.sdk.java.http;

import com.jlpay.open.jlpay.sdk.java.config.OrgConfig;
import com.jlpay.open.jlpay.sdk.java.sign.SignVerifier;
import com.jlpay.open.jlpay.sdk.java.sign.SignVerifierManager;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import java.util.concurrent.TimeUnit;

import static java.util.Objects.requireNonNull;

/**
 * 默认HttpClient构建器
 * <p>默认使用OkHttpClient作为底层实现，支持CloseableHttpClient
 *
 * @author zhaomeixia
 * @since 2024/2/20
 */
public class DefaultHttpClientBuilder {

    private static final int MAX_IDLE_CONNECTIONS = 5;
    private static final int KEEP_ALIVE_SECONDS = 10;
    private SignVerifierManager signVerifierManager;
    private OkHttpClient customizeOkHttpClient;
    private CloseableHttpClient customizeApacheHttpClient;
    private int readTimeoutMills = -1;
    private int writeTimeoutMills = -1;
    private int connectTimeoutMills = -1;
    private int maxConnections = 20;
    private int maxPerRoute = 20;
    private int keepAliveSeconds = 10;

    /**
     * 复制工厂，复制一个当前对象
     *
     * @return 对象的副本
     */
    public DefaultHttpClientBuilder newInstance() {
        DefaultHttpClientBuilder result = new DefaultHttpClientBuilder();
        result.signVerifierManager = this.signVerifierManager;
        result.customizeOkHttpClient = this.customizeOkHttpClient;
        result.customizeApacheHttpClient = this.customizeApacheHttpClient;
        result.readTimeoutMills = this.readTimeoutMills;
        result.writeTimeoutMills = this.writeTimeoutMills;
        result.connectTimeoutMills = this.connectTimeoutMills;
        result.maxConnections = this.maxConnections;
        result.maxPerRoute = this.maxPerRoute;
        result.keepAliveSeconds = this.keepAliveSeconds;
        return result;
    }

    /**
     * 设置读超时
     *
     * @param readTimeoutMills 读超时，单位毫秒
     * @return defaultHttpClientBuilder
     */
    public DefaultHttpClientBuilder readTimeoutMills(int readTimeoutMills) {
        this.readTimeoutMills = readTimeoutMills;
        return this;
    }

    /**
     * 设置写超时
     *
     * @param writeTimeoutMills 写超时，单位毫秒
     * @return defaultHttpClientBuilder
     */
    public DefaultHttpClientBuilder writeTimeoutMills(int writeTimeoutMills) {
        this.writeTimeoutMills = writeTimeoutMills;
        return this;
    }

    /**
     * 设置连接超时
     *
     * @param connectTimeoutMills 连接超时，单位毫秒
     * @return defaultHttpClientBuilder
     */
    public DefaultHttpClientBuilder connectTimeoutMills(int connectTimeoutMills) {
        this.connectTimeoutMills = connectTimeoutMills;
        return this;
    }

    public DefaultHttpClientBuilder maxConnections(int maxConnections) {
        this.maxConnections = maxConnections;
        return this;
    }

    public DefaultHttpClientBuilder maxPerRoute(int maxPerRoute) {
        this.maxPerRoute = maxPerRoute;
        return this;
    }

    public DefaultHttpClientBuilder keepAliveSeconds(int keepAliveSeconds) {
        this.keepAliveSeconds = keepAliveSeconds;
        return this;
    }

    /**
     * 设置凭据生成器
     *
     * @param signVerifierManager 凭据生成器
     * @return defaultHttpClientBuilder
     */
    public DefaultHttpClientBuilder signVerifierManager(SignVerifierManager signVerifierManager) {
        this.signVerifierManager = signVerifierManager;
        return this;
    }

    /**
     * 设置 okHttpClient 若设置该参数，会覆盖client中的原有配置
     *
     * @param okHttpClient 用户自定义的okHttpClient
     * @return defaultHttpClientBuilder
     */
    public DefaultHttpClientBuilder okHttpClient(OkHttpClient okHttpClient) {
        this.customizeOkHttpClient = okHttpClient;
        return this;
    }

    /**
     * 设置 CloseableHttpClient，若设置该参数，会优先使用此客户端
     *
     * @param apacheHttpClient 用户自定义的apacheHttpClient
     * @return defaultHttpClientBuilder
     */
    public DefaultHttpClientBuilder closeableHttpClient(CloseableHttpClient apacheHttpClient) {
        this.customizeApacheHttpClient = apacheHttpClient;
        return this;
    }

    public DefaultHttpClientBuilder config(OrgConfig config) {
        requireNonNull(config, "config is required");
        SignVerifier signVerifier = config.createSignerVerifier();
        this.signVerifierManager = new SignVerifierManager(signVerifier);
        return this;
    }

    /**
     * 构建默认HttpClient
     *
     * @return httpClient
     */
    public HttpClient build() {
        requireNonNull(signVerifierManager);

        if (customizeOkHttpClient != null) {
            return buildOkHttpClient();
        } else {
            return buildApacheHttpClient();
        }
    }


    private HttpClient buildApacheHttpClient() {
        if (customizeApacheHttpClient != null) {
            return new ApacheHttpClientWrapper(customizeApacheHttpClient, signVerifierManager);
        }

        PoolingHttpClientConnectionManager connManager =
                new PoolingHttpClientConnectionManager(keepAliveSeconds, TimeUnit.SECONDS);
        connManager.setMaxTotal(maxConnections);
        connManager.setDefaultMaxPerRoute(maxPerRoute);
        connManager.setValidateAfterInactivity(10000);

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(connectTimeoutMills)
                .setSocketTimeout(readTimeoutMills)
                .setConnectionRequestTimeout(writeTimeoutMills)
                .build();

        CloseableHttpClient client = HttpClients.custom()
                .setConnectionManager(connManager)
                .setDefaultRequestConfig(requestConfig)
                .setKeepAliveStrategy((response, context) -> keepAliveSeconds * 1000)
                .evictExpiredConnections()
                .evictIdleConnections(keepAliveSeconds, TimeUnit.SECONDS)
                .build();

        return new ApacheHttpClientWrapper(client, signVerifierManager);
    }

    private HttpClient buildOkHttpClient() {
        OkHttpClient.Builder okHttpClientBuilder =
                (customizeOkHttpClient == null ? buildDefaultOkHttpClient() : customizeOkHttpClient).newBuilder();
        if (connectTimeoutMills >= 0) {
            okHttpClientBuilder.connectTimeout(connectTimeoutMills, TimeUnit.MILLISECONDS);
        }
        if (readTimeoutMills >= 0) {
            okHttpClientBuilder.readTimeout(readTimeoutMills, TimeUnit.MILLISECONDS);
        }
        if (writeTimeoutMills >= 0) {
            okHttpClientBuilder.writeTimeout(writeTimeoutMills, TimeUnit.MILLISECONDS);
        }
        return new OkHttpClientWrapper(okHttpClientBuilder.build(), signVerifierManager);
    }

    private OkHttpClient buildDefaultOkHttpClient() {
        return new OkHttpClient.Builder()
                .connectionPool(new ConnectionPool(MAX_IDLE_CONNECTIONS, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS))
                .build();
    }
}
